Skip to content

feat(workflow-executor): scaffold @forestadmin/workflow-executor package#1493

Open
Scra3 wants to merge 9 commits intomainfrom
feat/prd-214-setup-workflow-executor-package
Open

feat(workflow-executor): scaffold @forestadmin/workflow-executor package#1493
Scra3 wants to merge 9 commits intomainfrom
feat/prd-214-setup-workflow-executor-package

Conversation

@Scra3
Copy link
Member

@Scra3 Scra3 commented Mar 17, 2026

Summary

  • Scaffold the new @forestadmin/workflow-executor package (tsconfig, jest, eslint, CI matrix)
  • CLAUDE.md with project overview and architecture principles (pull-based polling, atomic steps, privacy, port injection, AI integration)
  • No premature dependencies — they'll be added as needed (YAGNI)
  • Smoke test so CI lint+test passes

fixes PRD-214

Test plan

  • yarn workspace @forestadmin/workflow-executor build passes
  • yarn workspace @forestadmin/workflow-executor lint passes
  • yarn workspace @forestadmin/workflow-executor test passes
  • CI green on this branch

🤖 Generated with Claude Code

Note

Scaffold @forestadmin/workflow-executor package in the monorepo

Adds the initial package structure for @forestadmin/workflow-executor with an empty src/index.ts, TypeScript and ESLint configs, Jest setup, and a package.json configured for the workspace. The package is also added to the CI matrix in build.yml so it participates in build and test jobs.

Changes since #1493 opened

  • Defined workflow step type system including step definitions, execution data, and execution history types [29f5646]
  • Defined record and execution context type system [29f5646]
  • Defined port interfaces for external system integration [29f5646]
  • Exported package API through main module entry point [29f5646]
  • Added tests verifying exported module API [29f5646]
  • Implemented AI-powered workflow step execution via BaseStepExecutor abstract class and ConditionStepExecutor concrete implementation [127b579]
  • Introduced custom error classes for workflow execution failures [127b579]
  • Refactored execution types to support immutability, scoped context, and specialized step records [127b579]
  • Refactored step execution data types to move execution details into concrete types and support condition-specific fields [127b579]
  • Refactored step history types to introduce specialized status types and enforce privacy constraints [127b579]
  • Modified RunStore interface to scope all methods to the current run by removing runId parameter [127b579]
  • Tightened ConditionStepDefinition.options type constraint to require at least one option [127b579]
  • Added @langchain/core version 1.1.33 and zod version 4.3.6 as runtime dependencies to @forestadmin/workflow-executor package [127b579]
  • Exported new types, error classes, and executor classes from @forestadmin/workflow-executor package public API [127b579]
  • Added unit tests for BaseStepExecutor and ConditionStepExecutor [127b579]
  • Expanded architecture documentation in CLAUDE.md [127b579]
  • Refactored type system to replace RecordRef with CollectionRef and support composite primary keys as arrays [cb8036b]
  • Implemented AgentClientAgentPort adapter class with methods for record operations, related data access, and action execution [cb8036b]
  • Added RecordNotFoundError class, updated package exports, added @forestadmin/agent-client dependency, and created test suite for AgentClientAgentPort [cb8036b]
  • Introduced AiClient class in @forestadmin/ai-proxy package to manage AI model instances and MCP tool lifecycle [0ebae51]
  • Extracted configuration validation, model creation, and configuration resolution into standalone utility functions [0ebae51]
  • Refactored Router class in @forestadmin/ai-proxy to use extracted validation and configuration utilities [0ebae51]
  • Exported createBaseChatModel function and AiClient class from @forestadmin/ai-proxy package public API [0ebae51]
  • Added test coverage for AiClient class, createBaseChatModel function, and getAiConfiguration function [0ebae51]
  • Implemented ForestServerWorkflowPort adapter class that implements WorkflowPort interface using @forestadmin/forestadmin-client ServerUtils.query to communicate with Forest server endpoints [c25a953]
  • Renamed WorkflowPort interface method from completeStepExecution to updateStepExecution [c25a953]
  • Added @forestadmin/forestadmin-client version 1.37.17 as dependency to @forestadmin/workflow-executor package and exported ForestServerWorkflowPort from package index [c25a953]
  • Exported ServerUtils as named export from @forestadmin/forestadmin-client package [c25a953]
  • Renamed core domain types from StepHistory to StepOutcome, CollectionRef to CollectionSchema, RecordFieldRef to FieldSchema, ActionRef to ActionSchema, and removed 'id' field from BaseStepDefinition and 'type' field from FieldSchema [c9877fe]
  • Refactored ExecutionContext to be generic over TStep, replaced step and stepHistory parameters with context fields, and changed history structure from StepRecord entries to Step entries [c9877fe]
  • Updated BaseStepExecutor.buildPreviousStepsMessages to format step summaries with separate Input and Output lines instead of a single Result line, sourcing data from stepOutcome and matched step executions [c9877fe]
  • Changed ConditionStepExecutor.execute to be parameterless, return stepOutcome instead of stepHistory, and read step metadata from ExecutionContext [c9877fe]
  • Implemented ReadRecordStepExecutor to select records, choose relevant fields via tool-calling, fetch specified fields from the agent, and persist execution data [c9877fe]
  • Modified AgentPort.getRecord to accept optional fieldNames parameter and removed getActions method, updated AgentClientAgentPort to fetch only specified fields and return simplified RecordData [c9877fe]
  • Renamed WorkflowPort.getCollectionRef to getCollectionSchema and changed updateStepExecution to accept StepOutcome instead of StepHistory [c9877fe]
  • Removed record-centric methods from RunStore interface, retaining only getStepExecutions and saveStepExecution [c9877fe]
  • Expanded StepExecutionData union with ReadRecordStepExecutionData and LoadRelatedRecordStepExecutionData types, made ConditionStepExecutionData fields required, and added isExecutedStepOnExecutor helper function [c9877fe]
  • Added NoRecordsError, NoReadableFieldsError, and NoResolvedFieldsError classes extending WorkflowExecutorError [c9877fe]
  • Updated index.ts public exports to replace StepHistory exports with StepOutcome equivalents, add new execution data types and error classes, replace record types with schema types, and add ReadRecordStepExecutor [c9877fe]

Macroscope summarized 17f26ca.

@linear
Copy link

linear bot commented Mar 17, 2026

matthv and others added 2 commits March 17, 2026 15:00
…premature deps, add smoke test

- Rewrite CLAUDE.md with project overview and architecture principles, remove changelog
- Remove unused dependencies (ai-proxy, sequelize, zod) per YAGNI
- Add smoke test so CI passes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the feat/prd-214-setup-workflow-executor-package branch from 083894b to 4510b7b Compare March 17, 2026 14:00
@qltysh
Copy link

qltysh bot commented Mar 17, 2026

Qlty

Coverage Impact

⬆️ Merging this pull request will increase total coverage on main by 0.03%.

Modified Files with Diff Coverage (12)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/ai-proxy/src/router.ts100.0%
Coverage rating: A Coverage rating: A
packages/ai-proxy/src/index.ts100.0%
New file Coverage rating: A
packages/ai-proxy/src/get-ai-configuration.ts100.0%
New file Coverage rating: A
packages/ai-proxy/src/validate-ai-configurations.ts100.0%
New file Coverage rating: A
...ges/workflow-executor/src/executors/condition-step-executor.ts94.1%61
New file Coverage rating: A
packages/workflow-executor/src/types/step-definition.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
New file Coverage rating: A
packages/ai-proxy/src/create-base-chat-model.ts100.0%
New file Coverage rating: A
packages/ai-proxy/src/ai-client.ts100.0%
New file Coverage rating: A
...ages/workflow-executor/src/adapters/agent-client-agent-port.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/errors.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/index.ts100.0%
Total99.4%
🤖 Increase coverage with AI coding...

In the `feat/prd-214-setup-workflow-executor-package` branch, add test coverage for this new code:

- `packages/workflow-executor/src/executors/condition-step-executor.ts` -- Line 61

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

… document system architecture

- Lint now covers src and test directories
- Replace require() with import, use stronger assertion (toHaveLength)
- Add System Architecture section describing Front/Orchestrator/Executor/Agent
- Mark Architecture Principles as planned (not yet implemented)
- Remove redundant test/.gitkeep
- Make index.ts a valid module with export {}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
export type McpConfiguration = unknown;

export interface WorkflowPort {
getPendingStepExecutions(): Promise<PendingStepExecution[]>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method will retrieve the workflowRun and workflowSteps of one pending run. It will take an optional runId, and return an object with complete workflowRun, workflowSteps, workflowRecords

Comment on lines +5 to +21
interface BaseStepHistory {
stepId: string;
stepIndex: number;
status: StepStatus;
/** Present when status is 'error'. */
error?: string;
}

export interface ConditionStepHistory extends BaseStepHistory {
type: 'condition';
/** Present when status is 'success'. */
selectedOption?: string;
}

export interface AiTaskStepHistory extends BaseStepHistory {
type: 'ai-task';
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are those types ? this does not correspond to anything

};
}

// agent-client methods (update, relation, action) still expect the pipe-encoded string format
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low adapters/agent-client-agent-port.ts:25

encodePk joins primary key values with | without escaping, so a value containing a pipe (e.g., ['user|123', 'tenant1']) encodes to the same string as a different composite key (['user', '123', 'tenant1']). This causes update, relation, and action operations to target the wrong record when primary key values contain pipe characters.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around line 25:

`encodePk` joins primary key values with `|` without escaping, so a value containing a pipe (e.g., `['user|123', 'tenant1']`) encodes to the same string as a different composite key (`['user', '123', 'tenant1']`). This causes `update`, `relation`, and `action` operations to target the wrong record when primary key values contain pipe characters.

Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts:25-27 (REVIEWED_COMMIT) - encodePk function joins with '|' without escaping; packages/agent/src/utils/id.ts:26 - packId also joins with '|'; packages/agent/src/utils/id.ts:43 - unpackId splits with '|' without unescaping

return { ...ref, recordId, values: updatedRecord };
}

async getRelatedData(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium adapters/agent-client-agent-port.ts:76

getRelatedData passes relationName (the field name, e.g., 'author') to getCollectionRef, which looks up collection metadata by collection name. This returns a fallback CollectionRef with primaryKeyFields: ['id'] instead of the actual related collection's metadata. Consequently, extractRecordId extracts the wrong fields from related records, returning incorrect or undefined values for the primary key. Consider obtaining the target collection name from the relation's metadata rather than using the field name directly.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around line 76:

`getRelatedData` passes `relationName` (the field name, e.g., `'author'`) to `getCollectionRef`, which looks up collection metadata by collection name. This returns a fallback `CollectionRef` with `primaryKeyFields: ['id']` instead of the actual related collection's metadata. Consequently, `extractRecordId` extracts the wrong fields from related records, returning incorrect or `undefined` values for the primary key. Consider obtaining the target collection name from the relation's metadata rather than using the field name directly.

Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts lines 72-87 (getRelatedData method passes relationName to getCollectionRef at line 76), lines 100-112 (getCollectionRef looks up by collectionName and returns fallback with primaryKeyFields: ['id']), packages/workflow-executor/test/adapters/agent-client-agent-port.test.ts lines 152-174 (test only works because relation name 'posts' matches collection name 'posts'; fallback test confirms behavior when they don't match)

@qltysh
Copy link

qltysh bot commented Mar 18, 2026

2 new issues

Tool Category Rule Count
qlty Structure Function with many returns (count = 4): getAiConfiguration 2

Comment on lines +30 to +35
function extractRecordId(
primaryKeyFields: string[],
record: Record<string, unknown>,
): Array<string | number> {
return primaryKeyFields.map(field => record[field] as string | number);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low adapters/agent-client-agent-port.ts:30

extractRecordId returns undefined for missing primary key fields, but the string | number type assertion hides this. When passed to encodePk, undefined becomes the literal string "undefined", causing lookups to silently fail with wrong record IDs. Consider adding a runtime check for missing fields and throwing an error, or return the actual values and let encodePk validate them.

function extractRecordId(
  primaryKeyFields: string[],
  record: Record<string, unknown>,
): Array<string | number> {
-  return primaryKeyFields.map(field => record[field] as string | number);
+  return primaryKeyFields.map(field => {
+    const value = record[field];
+    if (value === undefined || value === null) {
+      throw new Error(`Missing primary key field: ${field}`);
+    }
+    return value as string | number;
+  });
}
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around lines 30-35:

`extractRecordId` returns `undefined` for missing primary key fields, but the `string | number` type assertion hides this. When passed to `encodePk`, `undefined` becomes the literal string `"undefined"`, causing lookups to silently fail with wrong record IDs. Consider adding a runtime check for missing fields and throwing an error, or return the actual values and let `encodePk` validate them.

Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts lines 25-33 (REVIEWED_COMMIT): `encodePk` uses `String(v)` which converts undefined to "undefined"; `extractRecordId` uses type assertion `as string | number` on `record[field]` which can be undefined at runtime. Line 83 shows `extractRecordId` is called in `getRelatedData` to create record IDs that are returned and could be used in subsequent operations.

Comment on lines +9 to +15
// TODO: finalize route paths with the team — these are placeholders
const ROUTES = {
pendingStepExecutions: '/liana/v1/workflow-step-executions/pending',
updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${runId}/complete`,
collectionRef: (collectionName: string) => `/liana/v1/collections/${collectionName}`,
mcpServerConfigs: '/liana/mcp-server-configs-with-details',
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low adapters/forest-server-workflow-port.ts:9

ROUTES.updateStepExecution(runId) and ROUTES.collectionRef(collectionName) interpolate raw values into URL paths without encoding, so special characters like /, ?, or % in runId or collectionName produce malformed URLs (e.g., collectionName="a/b" becomes /liana/v1/collections/a/b with three path segments). Consider wrapping the parameters with encodeURIComponent() to ensure they are safely encoded.

-// TODO: finalize route paths with the team — these are placeholders
 const ROUTES = {
   pendingStepExecutions: '/liana/v1/workflow-step-executions/pending',
-  updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${runId}/complete`,
-  collectionRef: (collectionName: string) => `/liana/v1/collections/${collectionName}`,
+  updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${encodeURIComponent(runId)}/complete`,
+  collectionRef: (collectionName: string) => `/liana/v1/collections/${encodeURIComponent(collectionName)}`,
   mcpServerConfigs: '/liana/mcp-server-configs-with-details',
 };
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/forest-server-workflow-port.ts around lines 9-15:

`ROUTES.updateStepExecution(runId)` and `ROUTES.collectionRef(collectionName)` interpolate raw values into URL paths without encoding, so special characters like `/`, `?`, or `%` in `runId` or `collectionName` produce malformed URLs (e.g., `collectionName="a/b"` becomes `/liana/v1/collections/a/b` with three path segments). Consider wrapping the parameters with `encodeURIComponent()` to ensure they are safely encoded.

Evidence trail:
packages/workflow-executor/src/adapters/forest-server-workflow-port.ts lines 11-13 (ROUTES definitions with template literals), packages/forestadmin-client/src/utils/server.ts lines 63-70 (queryWithHeaders passes path directly to new URL() without encoding)

Copy link
Member

@matthv matthv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want to merge it yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants